/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 * Marcus Cuda
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using dnAnalytics.Properties;

namespace dnAnalytics.LinearAlgebra.IO
{
    /// <summary>
    /// Creates matrices from Matrix Market files.
    /// </summary>
    /// <remarks>see <a href="http://math.nist.gov/MatrixMarket/formats.html">http://math.nist.gov/MatrixMarket/formats.html</a> for more information.</remarks>
    public class MatrixMarketReader : SingleMatrixReader
    {
        private static readonly Regex spaces = new Regex(" +");

        /// <summary>
        /// Reads in a matrix market file using an array layout.
        /// </summary>
        /// <param name="ret">The matrix to fill.</param>
        /// <param name="reader">The TextReader to read from.</param>
        /// <param name="format">The storage format to use.</param>
        /// <param name="rows">The number of rows.</param>
        private static void ArrayReader(Matrix ret, TextReader reader, StorageFormat format, int rows)
        {
            string line = reader.ReadLine();

            int row = 0;
            int col = 0;
            if (format == StorageFormat.SkewSymmetric)
            {
                row = 1;
            }
            while (line != null)
            {
                if (line.Length > 0)
                {
                    double value = Double.Parse(line.Trim(), NumberStyles.Any, null);
                    ret[row, col] = value;
                    if (format == StorageFormat.Symmetric)
                    {
                        ret[col, row] = value;
                    }
                    else if (format == StorageFormat.SkewSymmetric)
                    {
                        ret[col, row] = -value;
                    }

                    row++;
                    //reached end of column
                    if (row == rows)
                    {
                        row = 0;
                        col++;
                        if (format == StorageFormat.Symmetric)
                        {
                            row = col;
                        }
                        else if (format == StorageFormat.SkewSymmetric)
                        {
                            row = col + 1;
                        }
                    }
                }
                line = reader.ReadLine();
            }
        }

        /// <summary>
        /// Reads in a matrix market file using a coordinate layout.
        /// </summary>
        /// <param name="ret">The matrix to fill.</param>
        /// <param name="reader">The TextReader to use.</param>
        /// <param name="format">The storage format.</param>
        /// <param name="type">The data type.</param>
        private static void CoordinateReader(Matrix ret, TextReader reader, StorageFormat format, DataType type)
        {
            string line = reader.ReadLine();
            while (line != null)
            {
                if (line.Length > 0)
                {
                    string[] values = spaces.Split(line.Trim());
                    int row = Int32.Parse(values[0], CultureInfo.InvariantCulture) - 1;
                    int col = Int32.Parse(values[1], CultureInfo.InvariantCulture) - 1;

                    double value = 1;
                    if (type != DataType.Pattern)
                    {
                        value = Double.Parse(values[2], NumberStyles.Any, null);
                    }
                    ret[row, col] = value;
                    if (format == StorageFormat.Symmetric)
                    {
                        ret[col, row] = value;
                    }
                }
                line = reader.ReadLine();
            }
        }

        /// <summary>
        /// Performs the actual reading.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> to read the matrix from.</param>
        /// <param name="storageType">The <see cref="StorageType"/> for the new matrix.</param>
        /// <returns>
        /// A matrix containing the data from the <see cref="Stream"/>. <see langword="null"/> is returned if the <see cref="Stream"/> is empty.
        /// </returns>

        protected override Matrix DoReadMatrix(Stream stream, StorageType storageType)
        {
            TextReader reader = new StreamReader(new BufferedStream(stream));
            string line = reader.ReadLine().ToLowerInvariant();
            if (string.IsNullOrEmpty(line))
            {
                return null;
            }

            string[] args = spaces.Split(line);
            if (!args[0].Equals("%%matrixmarket"))
            {
                throw new FormatException(Resources.NotMatrixMarketFile);
            }
            if (args.Length < 5)
            {
                throw new FormatException(Resources.NotProperHeader);
            }

            if (!args[1].Equals("matrix"))
            {
                throw new FormatException(Resources.NotMatrixMarketFile);
            }

            bool isArray;
            if (args[2].Equals("array"))
            {
                isArray = true;
            }
            else if (args[2].Equals("coordinate"))
            {
                isArray = false;
            }
            else
            {
                throw new FormatException(Resources.NotProperlyFormattedMatrixMarketFile);
            }

            DataType type = DataType.Real;
            if (args[3].Equals("complex"))
            {
                throw new NotSupportedException(Resources.ComplexNotSupported);
            }
            if (args[3].Equals("pattern"))
            {
                type = DataType.Pattern;
            }

            StorageFormat format;
            if (args[4].Equals("general"))
            {
                format = StorageFormat.General;
            }
            else if (args[4].Equals("symmetric"))
            {
                format = StorageFormat.Symmetric;
            }
            else if (args[4].Equals("skew-symmetric"))
            {
                format = StorageFormat.SkewSymmetric;
            }
            else
            {
                throw new FormatException(Resources.NotProperlyFormattedMatrixMarketFile);
            }

            if (type == DataType.Pattern && format == StorageFormat.SkewSymmetric)
            {
                throw new FormatException(Resources.NotProperlyFormattedMatrixMarketFile);
            }

            //get matrix dimensions
            line = reader.ReadLine();

            //skip comments
            while (line.StartsWith("%", StringComparison.Ordinal) || line.Length == 0)
            {
                line = reader.ReadLine();
            }
            args = spaces.Split(line.Trim());
            if (args.Length < 2)
            {
                throw new FormatException(Resources.NotProperlyFormattedMatrixMarketFile);
            }

            int rows = Int32.Parse(args[0], CultureInfo.InvariantCulture);
            int cols = Int32.Parse(args[1], CultureInfo.InvariantCulture);

            Matrix ret = null;
            switch (storageType)
            {
                case StorageType.Dense:
                    ret = new DenseMatrix(rows, cols);
                    break;
                case StorageType.Sparse:
                    ret = new SparseMatrix(rows, cols);
                    break;
                default:
                    Debug.Assert(false);
                    break;
            }

            if (isArray)
            {
                if (type == DataType.Pattern)
                {
                    throw new FormatException(Resources.NotProperlyFormattedMatrixMarketFile);
                }
                ArrayReader(ret, reader, format, rows);
            }
            else
            {
                CoordinateReader(ret, reader, format, type);
            }
            reader.Close();
            reader.Dispose();
            return ret;
        }
    }
}
